#include "misc_utils.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>

void list_init(LIST_T* list)
{
    list->head = list->tail = NULL;
    list->size = 0;
}

void list_add_head(LIST_T* list, LIST_NODE_T* node)
{
    node->prev = NULL;
    node->next = list->head;
    if (list->head != NULL)
        list->head->prev = node;
    list->head = node;
    if (list->tail == NULL)
        list->tail = node;
    list->size++;
}

void list_add_tail(LIST_T* list, LIST_NODE_T* node)
{
    node->prev = list->tail;
    node->next = NULL;
    if (list->tail != NULL)
        list->tail->next = node;
    if (list->head == NULL)
        list->head = node;
    list->tail = node;
    list->size++;
}

void list_remove(LIST_T* list, LIST_NODE_T* node)
{
    LIST_NODE_T* p = node->prev;
    LIST_NODE_T* n = node->next;
    if (p != NULL)
        p->next = n;
    if (n != NULL)
        n->prev = p;
    node->prev = node->next = NULL;
    if (list->head == node)
        list->head = n;
    if (list->tail == node)
        list->tail = p;
    list->size--;
}

void list_remove_head(LIST_T* list)
{
    list_remove(list, list->head);
}

void list_remove_tail(LIST_T* list)
{
    list_remove(list, list->tail);
}


void list_insert_after(LIST_T* list, LIST_NODE_T* node, LIST_NODE_T* prev)
{
    if (prev == list->head)
        list_add_head(list, node);
    else
    {
        node->prev = prev;
        node->next = prev->next;
        prev->next->prev = node;
        prev->next = node;
        list->size++;
    }
}

void list_insert_before(LIST_T* list, LIST_NODE_T* node, LIST_NODE_T* next)
{
    if (next == list->tail)
        list_add_tail(list, node);
    else
    {
        node->next = next;
        node->prev = next->prev;
        next->prev->next = node;
        next->prev = node;
        list->size++;
    }
}

///////////////////////////////////////////////////////////////////////////////
// String functions
///////////////////////////////////////////////////////////////////////////////

STRING_T* string_new(const char* str)
{
    int len = strlen(str);
    STRING_T* result = (STRING_T*)malloc(sizeof(int) + len + 1);
    result->length = len;
    strcpy(result->data, str);
    return result;
}

void string_destroy(STRING_T* str)
{
    free(str);
}

STRING_T* string_cat(const char* a, const char* b)
{
    int alen = strlen(a);
    int blen = strlen(b);
    STRING_T* result = (STRING_T*)malloc(sizeof(int) + alen + blen + 1);
    char* p = result->data;
    while (1)
    {
        *p = *a;
        if (*a == 0)
            break;
        p++;
        a++;
    }
    while (1)
    {
        *p = *b;
        if (*b == 0)
            break;
        p++;
        b++;
    }
    result->length = alen + blen;
    return result;
}

STRING_T* string_printf(const char* fmt, ...)
{
    STRING_T* result = NULL;
    va_list vl;
    // attempt to get needed size
    va_start(vl, fmt);
    result = string_vprintf(fmt, vl);
    va_end(vl);

    return result;
}

STRING_T* string_vprintf(const char* fmt, va_list vl)
{
    char buffer[2];
    int size_needed = 0;
    STRING_T* result = NULL;
    va_list vl_bak;
    va_copy(vl_bak, vl);
    // attempt to get needed size

    size_needed = vsnprintf(buffer, sizeof(buffer), fmt, vl_bak);

    result = (STRING_T*)malloc(sizeof(int) + size_needed + 1);

    result->length = vsprintf(result->data, fmt, vl);
    return result;
}

// returns an array, terminated by NULL. Empty strings are not included.
STRING_T** string_split(const char* str, const char* delimit)
{
    // first pass, get string count
    int ary_size = 1;
    const char* p = str;
    int needle_size = strlen(delimit);
    STRING_T** result = NULL;
    int index = 0;

    while ((p != NULL) && (*p != 0))
    {
        p = strstr(p, delimit);
        if (p != NULL)
        {
            p += needle_size;
            if (*p != 0)
                ary_size++;
        }
    }

    // allocate array
    result = (STRING_T**)malloc(sizeof(STRING_T*) * (ary_size + 1));
    // second pass, actually construct the array
    p = str;
    while ((p != NULL) && (*p != 0))
    {
        const char* old_p = p;
        int len = 0;
        p = strstr(p, delimit);
        if (p != NULL)
        {
            len = p - old_p;
            result[index++] = string_substr(old_p, 0, len);
            p += needle_size;
        }
        else
        {
            result[index++] = string_new(old_p);
        }
    }
    result[index] = NULL;
    return result;
}

STRING_T* string_substr(const char* str, int start, int len)
{
    int str_len = strlen(str);
    STRING_T* result = NULL;
    const char *b;
    char* a;

    if ((start >= str_len) || (len < 0))
        return NULL;

    if (start + len >= str_len)
        len = str_len - start;
    result = (STRING_T*)malloc(sizeof(int) + len + 1);
    result->length = len;
    a = result->data;
    b = str + start;
    while ((*a++ = *b++) && (b < (str + start + len)));
    *a = 0;
    return result;
}

int string_find(const char* str, const char* needle)
{
    const char* p = strstr(str, needle);
    if (p == NULL)
        return -1;
    return p - str;
}

int string_equal(const char* a, const char* b)
{
    return !strcmp(a, b);
}

STRING_T* string_trim(const char* str) // remove leading and trailing spaces
{
    int str_len = strlen(str);
    int leading_spaces = 0;
    int trailing_spaces = 0;

    while (isspace(str[leading_spaces]))
        leading_spaces++;
    while (1)
    {
        int index = str_len - trailing_spaces - 1;
        if ((index >= 0) && (isspace(str[index])))
            trailing_spaces++;
        else
            break;
    }
    return string_substr(str, leading_spaces, str_len - leading_spaces - trailing_spaces);
}

///////////////////////////////////////////////////////////////////////////////
// property file
///////////////////////////////////////////////////////////////////////////////
typedef struct PROP_ITEM_T
{
    LIST_NODE_T link;
    STRING_T* key;
    STRING_T* value;

} PROP_ITEM_T;

struct PROP_FILE_T
{
    LIST_T props;
};

PROP_FILE_T* prop_parse(FILE* file)
{
    PROP_FILE_T* prop = NEW(PROP_FILE_T);
    list_init(&prop->props);

    while (!feof(file))
    {
        int hash = 0, index = 0;
        STRING_T *line;
        STRING_T *temp;
        STRING_T** ary;
        char buffer[200];   // This is arbitrary. May be risky.
        if (NULL == fgets(buffer, sizeof(buffer), file))
            break;
        // special case: end with '.'.
        if (buffer[0] == '.')
            break;
        hash = string_find(buffer, "#");
        if (hash >= 0)
            temp = string_substr(buffer, 0, hash);
        else
            temp = string_new(buffer);
        // trim
        line = string_trim(temp->data);
        string_destroy(temp);
        // split
        if (line == NULL)
            continue;
        ary = string_split(line->data, "=");
        if ((ary[0] != NULL) && (ary[1] != NULL) && (ary[2] == NULL))
        {
            // this is the key/value line. Both key and value are trimmed.
            PROP_ITEM_T* item = NEW(PROP_ITEM_T);
            item->key = string_trim(ary[0]->data);
            item->value = string_trim(ary[1]->data);
            list_add_tail(&prop->props, &item->link);
        }
        for (index = 0; ary[index] != 0; index++)
            string_destroy(ary[index]);
        free(ary);
    }
    return prop;
}

void prop_destroy(PROP_FILE_T* prop)
{
    while (prop->props.size > 0)
    {
        PROP_ITEM_T* item = CONTAINER_PTR(PROP_ITEM_T, link, prop->props.head);
        list_remove_head(&prop->props);
        string_destroy(item->key);
        string_destroy(item->value);
        DELETE(item);
    }
    DELETE(prop);
}

STRING_T* prop_get(PROP_FILE_T* prop, const char* key)
{
    LIST_NODE_T* node;
    for (node = prop->props.head; node != NULL; node = node->next)
    {
        PROP_ITEM_T* item = CONTAINER_PTR(PROP_ITEM_T, link, node);
        if (string_equal(item->key->data, key))
            return item->value;
    }
    return NULL;
}

STRING_T* prop_get_fmt(PROP_FILE_T* prop, const char* key_fmt, ...)
{
    STRING_T* key;
    STRING_T* result;
    va_list vl;

    va_start(vl, key_fmt);
    key = string_vprintf(key_fmt, vl);
    va_end(vl);

    result = prop_get(prop, key->data);
    string_destroy(key);
    return result;
}

///////////////////////////////////////////////////////////////////////////////
// packet buffer
///////////////////////////////////////////////////////////////////////////////
void packet_buffer_init(PACKET_BUFFER_T* pb)
{
    pb->buffer_length = 64; // this is the default buffer size
    pb->buffer = malloc(pb->buffer_length);
    pb->data_length = 0;
    pb->index = 0;
}

static inline void packet_buffer_ensure_room(PACKET_BUFFER_T* pb, int size_needed)
{
    if (pb->data_length + size_needed > pb->buffer_length)
    {
        void* new_ptr = realloc(pb->buffer, pb->buffer_length * 2);
        assert(new_ptr != NULL);
        pb->buffer_length *= 2;
    }
}

void packet_buffer_parse(PACKET_BUFFER_T* pb, unsigned char* data, int len)
{
    pb->buffer = data;
    pb->buffer_length = len;
    pb->data_length = len;
    pb->index = 0;
}

void packet_buffer_destroy(PACKET_BUFFER_T* pb)
{
    if (pb->buffer != NULL)
    {
        free(pb->buffer);
        pb->buffer = NULL;
        pb->buffer_length = 0;
    }
    pb->data_length = 0;
    pb->index = 0;
}

void packet_buffer_clear(PACKET_BUFFER_T* pb)
{
    pb->data_length = 0;
    pb->index = 0;
}

void packet_buffer_get_data(PACKET_BUFFER_T* pb, unsigned char** p_data, int* p_len)
{
    *p_data = pb->buffer;
    *p_len = pb->data_length;
}

void packet_buffer_push_uint8(PACKET_BUFFER_T* pb, unsigned char data)
{
    packet_buffer_ensure_room(pb, 1);
    pb->buffer[pb->data_length] = data;
    pb->data_length++;
}

void packet_buffer_push_uint16(PACKET_BUFFER_T* pb, unsigned short data)
{
    packet_buffer_ensure_room(pb, 2);
    // This is risky. It may have alignment problem?
    *(unsigned short*)&pb->buffer[pb->data_length] = htons(data);
    pb->data_length += 2;
}

void packet_buffer_push_uint32(PACKET_BUFFER_T* pb, unsigned int data)
{
    packet_buffer_ensure_room(pb, 4);
    // This is risky. It may have alignment problem?
    *(unsigned int*)&pb->buffer[pb->data_length] = htonl(data);
    pb->data_length += 4;
}

void packet_buffer_set_uint8(PACKET_BUFFER_T* pb, int index, unsigned char data)
{
    assert(index < pb->data_length);
    pb->buffer[index] = data;
}

void packet_buffer_set_uint16(PACKET_BUFFER_T* pb, int index, unsigned short data)
{
    assert(index + 2 <= pb->data_length);
    *(unsigned short*)&pb->buffer[index] = htons(data);
}

void packet_buffer_set_uint32(PACKET_BUFFER_T* pb, int index, unsigned int data)
{
    assert(index + 4 <= pb->data_length);
    *(unsigned int*)&pb->buffer[index] = htonl(data);
}

int packet_buffer_shift_uint8(PACKET_BUFFER_T* pb, unsigned char* p_data)
{
    if (pb->index < pb->data_length)
    {
        *p_data = pb->buffer[pb->index];
        pb->index++;
        return 1;
    }
    return 0;
}

int packet_buffer_shift_uint16(PACKET_BUFFER_T* pb, unsigned short* p_data)
{
    if (pb->index + 2 <= pb->data_length)
    {
        *p_data = ntohs(*(unsigned short*)&pb->buffer[pb->index]);
        pb->index += 2;
        return 1;
    }
    return 0;
}

int packet_buffer_shift_uint32(PACKET_BUFFER_T* pb, unsigned int* p_data)
{
    if (pb->index + 4 <= pb->data_length)
    {
        *p_data = ntohl(*(unsigned int*)&pb->buffer[pb->index]);
        pb->index += 4;
        return 1;
    }
    return 0;
}

unsigned char packet_buffer_get_uint8(PACKET_BUFFER_T* pb, int index)
{
    assert(index < pb->data_length);
    return pb->buffer[index];
}

unsigned short packet_buffer_get_uint16(PACKET_BUFFER_T* pb, int index)
{
    assert(index + 2 <= pb->data_length);
    return ntohs(*(unsigned short*)&pb->buffer[index]);
}

unsigned int packet_buffer_get_uint32(PACKET_BUFFER_T* pb, int index)
{
    assert(index + 4 <= pb->data_length);
    return ntohl(*(unsigned int*)&pb->buffer[index]);
}
